我們在 Day5 提到了 JS 有非同步的機制,也因為這個機制,所以不會讓同步執行的 JS 阻塞,遇到 AJAX、 timer 等等的操作,都會先丟到事件佇列中,然後再慢慢拉回執行。
但是當 callback function 一複雜起來,將會導致回調地域,也就是 callback function 中包了更多的 callback function,原意是想等待前面一件事情完成後,再接著做新的事情,但卻導致程式碼變成很深的巢狀,也喪失了可讀性與不好維護。
Promise 優化了非同步執行的流程語法,使用上也很簡單,
Promise,處理 成功 與 失敗 狀態then 串接catch 處理錯誤(失敗)then 和 catch 的用法在昨日 Day22 已經展示,但上方第一點提到的成功與失敗是什麼意思呢?
Promise 顧名思義就是「承諾」的意思,既然是承諾,必定會告知對方結果,像是某A先生與網美小花告白,小花是個有禮貌的人,不會直接無聲卡,所以她一定會回應某A先生,不論這個回應是好消息還是壞消息,而且這個狀態一旦回應,就不能再更改,也就是說網美小花只要接受某A先生,就不能夠反悔。
寫成程式碼大概就長這樣
var willTogether = function(money) {
return new Promise(function (resolve, reject) {
if(money > 100) {
resolve("我也愛你,A先生");
} else {
reject(new Error('你是一個好人,但你值得更好的人'));
}
});
}
willTogether(10)
.then(function(val) {
console.log(val);
})
.catch(function(err) {
console.log(err);
});

從上方的範例中可以得知,Promise 總共有三個狀態
pending: 等待中、還未給出回應。resolve: 完成/成功reject: 失敗隔日某A先生撿到一些錢後...(純屬玩笑,請勿認真Q_Q)

好啦,總之就是 then 會接收 resolve 的 value,catch 則處理 reject 狀態。
而每個 then 可以再回傳一個 Promise(或值),它的結果會傳入下一個 then,所以可以一直串下去,也不會寫成醜醜的回調地域。
但是使用上需要注意,catch 如果是串在最後面,只要中途發生了 reject,將導致後面的 then 不會被執行,像是這樣:
willTogether(500)
.then(function(val) {
console.log("A1", val); // A1 正常印出
return willTogether(50);
})
.then(function(val) {
console.log("A2", val); // A2 不會印出
return willTogether(300);
})
.then(function(val) {
console.log("A3", val); // A3 不會印出
return willTogether(200);
})
.then(function(val){
console.log("A4", val); // A4 不會印出
})
.catch(function(err) {
console.log(err); // sorry
});
A1 如期印出,但是 A2、A3、A4 則不會印出,因為 willTogether(50) 回傳的狀態是 reject,所以直接被送到離它最近的 catch 處理,而 A2、A3、A4 也就不會被執行了。

如果改成這樣:
willTogether(500)
.then(function(val) {
console.log("A1", val); // A1 正常印出
return willTogether(50);
})
.catch(function(err) {
console.log(err); // sorry
return "keep going";
})
.then(function(val) {
console.log("A2", val); // A2 keep going
return willTogether(300);
})
.then(function(val) {
console.log("A3", val); // A3 正常印出
return willTogether(200);
})
.then(function(val){
console.log("A4", val); // A4 正常印出
})
.catch(function(err) {
console.log(err); // 不會印出
});
多在 willTogether(50) 串一個 catch 來收它的錯誤,則在印出錯誤訊息後,回傳 keep going 字串,繼續 then 執行下去,所以 A2 印出 keep going(不一定要回傳,不回傳下一個 then 會收到 undefined),之後的 A3、A4 則正常印出。

其實 catch 只是 then 的縮寫(語法糖)而已,then 完整寫法應該是這樣:
.then(function(val){
// if resolve
...
}, function(err){
// if reject
...
})
也就是其實 then 自己就可以處理 reject,不需要 catch,只是平常直接省略後面接收 reject 的函數,因為在沒有處理 reject 的情況下,每個 then 都寫出兩個函數來會讓程式碼很亂。
而 catch 則是 then 縮寫成這樣:
.then(undefined, function(err){
// if reject
...
})
那我們把上面的程式碼改一下,把 A2 所在的那個 then 多加一個 reject function,
willTogether(500)
.then(function(val) {
console.log("A1", val); // A1 正常印出
return willTogether(50);
})
.then(function(val) {
console.log("A2", val); // A2 不會印出
return willTogether(300); // 也不會回傳
}, function(err){
console.log(err); // sorry
return "keep going"; // 這個才會回傳
})
.then(function(val) {
console.log("A3", val); // A3 keep going
return willTogether(200);
})
.then(function(val){
console.log("A4", val); // A4 正常印出
})
.catch(function(err) {
console.log(err); // 不會印出
});
我們可以看到 A2 不會印出,因為前一個 Promise 回傳的狀態是 reject,所以 A2 這個 then 接收到後,是執行第二個 reject 那個函數,也就是印出錯誤訊息後,再回傳一個 keep going 的字串。

resolve 不一定要寫入回傳值,也可以 resolve() 回傳 undefined。
只要回傳的狀態不是 reject,就算當前是負責處理 reject 的 callback function,它 return 的值一樣會進入下一個 then 的 resolve function 中,所以範例中的 reject function,它回傳的 keep going 字串才會繼續被傳下去,而沒有進入下一個 then 的 reject function。
範例中網美小花總是很快回應,所以看不出 Promise 的好處。如果今天的狀況是:
「網美小花對於每個追求者有不固定的思考時間,等待思考結束後才會給回應(接受、拒絕),但是追求者也因為很有禮貌,所以會等網美小花回應前一個追求者後,才會進行追求」
這樣的狀況,就複雜了很多。
還不會 Promise 之前的你,是不是準備在 callback function 中寫更多的 callback function 來處理每個追求者呢?
至此有沒有感受到 Promise 的力量了XD
Promise的重點在於將非同步的 function 執行流程化,藉由then來順序的執行,catch對於錯誤的處理機制也更方便。
其實在簡單的情況下,可以不用建立一個完整的 Promise,可以使用
Promise.resolve
Promise.reject
像是上方的範例可以改寫成,
var willTogether = function(money) {
if(money > 100) {
return Promise.resolve("我也愛你,A先生");
} else {
return Promise.reject(new Error('你是一個好人,但你值得更好的人'));
}
}
恭喜你已經會了 Promise 基本的用法,但是還沒完,明天會再講講 race、all、async、await,加油!
文章盡量使用淺白的方式來介紹,如果導致專有名詞表達不是那麼精準,還請包容,也可以留言幫忙補充,學習的路上有你有我~
今日的分享就到這,我們明天見![]()